package com.liunian.channelhandler.handler;

import com.liunian.ServiceConfig;
import com.liunian.WrpcBootstrap;
import com.liunian.protection.RateLimiter;
import com.liunian.protection.TokenBuketRateLimiter;
import com.liunian.transport.message.RequestPayload;
import com.liunian.transport.message.WrpcRequest;
import com.liunian.transport.message.WrpcRespose;
import com.liunian.wrpcenum.RequestType;
import com.liunian.wrpcenum.ResponseCode;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Map;

@Slf4j
public class MethodCallHandler extends SimpleChannelInboundHandler<WrpcRequest> {

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, WrpcRequest wrpcRequest) throws Exception {
        // 1、先封装部分响应
        WrpcRespose wrpcRespose = new WrpcRespose();
        wrpcRespose.setRequestId(wrpcRequest.getRequestId());
        wrpcRespose.setCompressType(wrpcRequest.getCompressType());
        wrpcRespose.setSerializeType(wrpcRequest.getSerializeType());

        // 2、完成限流相关的操作
        // 我们如果要针对性的限流，应该一个地址匹配一个限流器
        Channel channel = channelHandlerContext.channel();
        SocketAddress socketAddress = channel.remoteAddress();
        Map<SocketAddress, RateLimiter> everyIpRateLimiter = WrpcBootstrap.getInstance().getConfiguration().getEveryIpRateLimiter();
        RateLimiter rateLimiter = everyIpRateLimiter.get(socketAddress);
        if (rateLimiter == null) {
            rateLimiter = new TokenBuketRateLimiter(20, 20);
            everyIpRateLimiter.put(socketAddress, rateLimiter);
        }
        // 限流
        boolean allowRequest = rateLimiter.allowRequest();
        if (!allowRequest) {
            // 需要封装响应并且返回
            wrpcRespose.setCode(ResponseCode.RATE_LIMIT.getCode());
        } else if (wrpcRequest.getRequestType() == RequestType.HEART_BEAT.getId()) {  // 处理心跳
            // 需要封装响应并且返回
            wrpcRespose.setCode(ResponseCode.SUCCESS_HEART_BEAT.getCode());
        } else {   // 正常调用

            /**
             * ---------------------------------具体的调用过程----------------------------------
             */
            // 1、获取负载内容
            RequestPayload requestPayload = wrpcRequest.getRequestPayload();
            // 2、根据负载内容进行方法调用
            try {
                Object result = CallTargetMethod(requestPayload);
                if (log.isDebugEnabled()) {
                    log.debug("请求[{}]已经在服务端完成方法调用。", wrpcRequest.getRequestId());
                }
                // 3、封装响应  是否需要考虑另外一个问题，响应码，响应类型。
                wrpcRespose.setCode(ResponseCode.SUCCESS.getCode());
                wrpcRespose.setBody(result);
            } catch (Exception e) {
                log.error("编号为[{}]的请求在调用过程中发生异常。",wrpcRequest.getRequestId(),e);
                wrpcRespose.setCode(ResponseCode.FAIL.getCode());
            }
        }
        // 4、写出响应
        channel.writeAndFlush(wrpcRespose);
    }

    private Object CallTargetMethod(RequestPayload requestPayload) {

        String interfaceName = requestPayload.getInterfaceName();
        String methodName = requestPayload.getMethodName();
        Class<?>[] parametersType = requestPayload.getParametersType();
        Object[] parametersValue = requestPayload.getParametersValue();

        // 寻找到匹配的暴露出去的具体的实现
        ServiceConfig<?> serviceConfig = WrpcBootstrap.SERVICE_LIST.get(interfaceName);
        Object refImpl = serviceConfig.getRef();
        // 通过反射调用 1、获取方法对象 2、执行invoke方法
        Object returnValue;
        try {
            Class<?> aClass = refImpl.getClass();
            Method method = aClass.getMethod(methodName, parametersType);
            returnValue = method.invoke(refImpl, parametersValue);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            log.error("调用服务[{}]的方法[{}]时发生异常。", interfaceName, methodName, e);
            throw new RuntimeException(e);
        }
        return returnValue;
    }
}
